package com.qing.annotation.singleton;

import com.google.auto.service.AutoService;
import com.qing.annotation.singleton.module.SingletonAnnotatedClass;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.util.*;

/**
 *
 * {@link Singleton}的Processor
 *
 * @author qing <br/>
 * 2023/9/24
 */
@AutoService(Processor.class)
public class SingletonProcessor extends AbstractProcessor {

    private Types mTypes;

    private Elements mElementUtils;

    private Filer mFiler;

    private Messager mMessager;

    private final List<SingletonAnnotatedClass> mAnnotatedClasses = new ArrayList<>();

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(Singleton.class.getCanonicalName());
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mTypes = processingEnv.getTypeUtils();
        mElementUtils = processingEnv.getElementUtils();
        mFiler = processingEnv.getFiler();
        mMessager = processingEnv.getMessager();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 遍历所有Singleton注解
        for (Element element : roundEnvironment.getElementsAnnotatedWith(Singleton.class)) {

            // 过滤非Class类型
            if (!element.getKind().isClass()) {
                error(element, "Only Classes can be annotation with @%s", Singleton.class.getSimpleName());
                return true;
            }

            TypeElement typeElement = (TypeElement) element;

            SingletonAnnotatedClass annotatedClass = new SingletonAnnotatedClass(typeElement);

            if (!isValidClass(annotatedClass)) {
                // 非有效类
                return true;
            }

            mAnnotatedClasses.add(annotatedClass);
        }

        try {
            for (SingletonAnnotatedClass mAnnotatedClass : mAnnotatedClasses) {
                mAnnotatedClass.generateCode(mElementUtils, mFiler);
            }
            mAnnotatedClasses.clear();
        } catch (IOException e) {
            error(null, e.getMessage());
        }

        return false;
    }

    /**
     * 判断注解类是否有效 <br>
     * 有效类型需满足: public修饰、非抽象类
     *
     * @param annotatedClass 待判断注解类
     * @return true --> 有效类, false --> 非法类
     */
    private boolean isValidClass(SingletonAnnotatedClass annotatedClass) {
        TypeElement typeElement = annotatedClass.getTypeElement();

        // 非public修饰
        if (!typeElement.getModifiers().contains(Modifier.PUBLIC)) {
            error(typeElement, "The class %s is not public", typeElement.getQualifiedName().toString());
            return false;
        }

        // 抽象类
        if (typeElement.getModifiers().contains(Modifier.ABSTRACT)) {
            error(typeElement, "The class %s is abstract. You can't annotate abstract classes with @%", typeElement.getQualifiedName().toString(), Singleton.class.getSimpleName());
            return false;
        }

        // 检查是否提供了默认公开(无参、公开)构造函数
        for (Element enclosed : typeElement.getEnclosedElements()) {
            if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {
                ExecutableElement constructorElement = (ExecutableElement) enclosed;
                if (constructorElement.getParameters().size() == 0 && constructorElement.getModifiers().contains(Modifier.PUBLIC)) {
                    // 找到了默认构造函数
                    return true;
                }
            }
        }

        // 没有找到默认构造函数
        error(typeElement, "The class %s must provide an public empty arguments default constructor", typeElement.getQualifiedName().toString());
        return true;
    }

    private void error(Element element, String msg, Object... args) {
        mMessager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args), element);
    }
}
